#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

#include <boost/thread/xtime.hpp>
#include <boost/thread/thread.hpp>

#include "base/logging.h"
#include "fcgi_service/fcgi_service.h"

using namespace std;
using namespace fcgi;

struct StatInfo {
  StatInfo() : pv(0), rank_count(0), rank_score(0) { }
  StatInfo(int p, int c, int s) : pv(p), rank_count(c), rank_score(s) { }
  float average_score() const {
    if (rank_count > 0) {
      return rank_score * 1.0 / rank_count;
    }
    return 0.0;
  }
  int pv;
  int rank_count;
  int rank_score; //总得分
};

class ArticleStatCache {
public:
  static ArticleStatCache & Instance() {
    static ArticleStatCache instance; 
    return instance;
  }
  StatInfo GetStat(int article) {
    {
      boost::shared_lock<boost::shared_mutex> lock(shared_mutex_);
      ArticleStats::const_iterator it = stat_cache_.find(article);
      if (it != stat_cache_.end()) {
        return it->second;
      }
    }
    return StatInfo(); 
  }
  void AddPv(int article, int delta) {
    if (delta <= 0) {
      LOG_INFO("article " << article << " bad pv-delta=" << delta);
      return;
    }
    {
      boost::unique_lock<boost::shared_mutex> lock(shared_mutex_);
      stat_cache_[article].pv += delta;
    }
    if (++delta_count_ > 20) {
      Flush();
    }
  }
  StatInfo Rank(int article, int score) {
    StatInfo ret;
    if (score > 0 || score <= 5) {
      boost::unique_lock<boost::shared_mutex> lock(shared_mutex_);
      StatInfo & stat = stat_cache_[article];
      stat.rank_count += 1;
      stat.rank_score += score;
      ret = stat;
      delta_count_ += 5;
    }

    if (delta_count_ > 20) {
      Flush();
    }
    return ret;
  }
private:
  typedef map<int, StatInfo> ArticleStats;
  ArticleStats stat_cache_;

  boost::shared_mutex shared_mutex_;
  int delta_count_;
  std::string data_file_;

  bool Flush() {
    FILE * data = fopen(data_file_.c_str(), "a+");
    if (!data) {
      LOG_WARN("flush stat file " << data_file_ << "error");
      return false;
    }
    static const int kMaxLine = 128;
    char line[kMaxLine];
    line[kMaxLine - 1] = 0;
    {
      boost::shared_lock<boost::shared_mutex> lock(shared_mutex_);
      ArticleStats::const_iterator it = stat_cache_.begin();
      for(; it != stat_cache_.end(); ++it) {
        snprintf(line, kMaxLine - 1, "%d %d %d %d\r\n", it->first, it->second.pv, it->second.rank_count, it->second.rank_score);
        printf("%s", line);
        fputs(line, data);
      }
    }
    fclose(data);
    delta_count_ = 0;
    LOG_INFO("stat data flush to " << data_file_);
    return true;
  }

  bool Reload() {
    FILE * data = fopen(data_file_.c_str(), "r");
    if (!data) {
      LOG_WARN("load from stat file " << data_file_ << " error");
      return false;
    }
    static const int kMaxLine = 128;
    char line[kMaxLine];
    line[kMaxLine - 1] = 0;
    std::vector<std::string> strs;
    int article, pv, rank_count, rank_score;
    while(fgets(line, kMaxLine - 1, data)) {
      printf("%s", line);
      boost::split(strs, line, boost::is_any_of(" \t\r\n"), boost::token_compress_on);
      if (strs.size() < 4) {
        continue;
      }
      try {
        article = boost::lexical_cast<int>(strs[0]);
        pv = boost::lexical_cast<int>(strs[1]);
        rank_count = boost::lexical_cast<int>(strs[2]);
        rank_score = boost::lexical_cast<int>(strs[3]);
      } catch (boost::bad_lexical_cast & e) {
        LOG_WARN("load bad stat data : " << line);
        continue;
      }
      if (article > 0) {
        boost::unique_lock<boost::shared_mutex> lock(shared_mutex_);
        stat_cache_.insert(make_pair(article, StatInfo(pv, rank_count, rank_score)));
      }
    }
    fclose(data);
    return true;
  }
  ArticleStatCache() : delta_count_(0), data_file_("./article_stat.txt") {
    Reload();
  }
  ArticleStatCache(const ArticleStatCache &); 
};

class LoadRequest : public Request {
public:
  LoadRequest(FCGX_Request* r) : Request(r) {}
  virtual bool Response() {
    if(!fcgi_out_) {
      LOG_INFO("fcgi_out NULL");
      return false;
    }
    int article_id = GetQueryInt("aid", -1);
    FCGX_PutS("Content-type: text/html\r\n\r\n", fcgi_out_);
    StatInfo stat;
    if (article_id > 0) {
      ArticleStatCache::Instance().AddPv(article_id, 1);
      stat = ArticleStatCache::Instance().GetStat(article_id);
    }
    FCGX_FPrintF(fcgi_out_, "{\"pv\":%d, \"count\":%d, \"score\":%.2f}", stat.pv, 
        stat.rank_count, stat.average_score());
    return true;
  }
};

class PvRequest : public Request {
public:
  PvRequest(FCGX_Request* r) : Request(r) {}
  virtual bool Response() {
    if(!fcgi_out_) {
      LOG_INFO("fcgi_out NULL");
      return false;
    }
    int article_id = GetQueryInt("aid", -1);
    FCGX_PutS("Content-type: text/html\r\n\r\n", fcgi_out_);
    if (article_id > 0) {
      ArticleStatCache::Instance().AddPv(article_id, 1);
    }
    FCGX_FPrintF(fcgi_out_, "%d", article_id);
    return true;
  }
};

class RankRequest : public Request {
public:
  RankRequest(FCGX_Request* r) : Request(r) {}
  virtual bool Response() {
    if(!fcgi_out_) {
      LOG_INFO("fcgi_out NULL");
      return false;
    }
    int article_id = GetQueryInt("aid", -1);
    int score = GetQueryInt("score", -1);
    FCGX_PutS("Content-type: text/html\r\n\r\n", fcgi_out_);

    StatInfo stat;
    if (article_id > 0) {
      stat = ArticleStatCache::Instance().Rank(article_id, score);
    }
    FCGX_FPrintF(fcgi_out_, "{\"count\":%d, \"score\":%.2f}", stat.rank_count, stat.average_score());
    return true;
  }
};

class DemoRequest : public Request {
public:
  DemoRequest(FCGX_Request* r) : Request(r) {}
  virtual bool Response();
};

bool DemoRequest::Response() {
  static int count = 0;
  if(!fcgi_out_) {
    cout << "remove notify fcgi_out null";
    return false;
  }

  stringstream rsp_header;
  rsp_header << "Content-type: text/html\r\n" << "Set-Cookie: count="
             << ++count << "\r\n\r\n";
  FCGX_PutS(rsp_header.str().c_str(), fcgi_out_);

  string rsp_body;
  rsp_body += "<span style=\"color:red\">query:</span>:</br>";
  for(map<string, string>::const_iterator it = query_map_.begin(); it != query_map_.end(); ++it) {
    rsp_body += it->first;
    rsp_body += ":";
    rsp_body += it->second;
    rsp_body += "</br>";
  }

  rsp_body += "<span style=\"color:red\">cookies:</span>:</br>";
  for(map<string, string>::const_iterator it = cookie_map_.begin(); it != cookie_map_.end(); ++it) {
    rsp_body += it->first;
    rsp_body += ":";
    rsp_body += it->second;
    rsp_body += "</br>";
  }

  FCGX_PutS(rsp_body.c_str(), fcgi_out_);
  return true;
}

class DemoRequestFactory : public RequestFactory {
public:
  virtual RequestPtr Create(FCGX_Request * r) {
    char * path = FCGX_GetParam("SCRIPT_NAME", r->envp);
    RequestPtr req;
    if (path) {
      if (strcmp(path, "/cgi/demo") == 0) {
        req = RequestPtr(new DemoRequest(r));
      } else if (strcmp(path, "/cgi/stat-load") == 0) {
        req = RequestPtr(new LoadRequest(r));
      } else if (strcmp(path, "/cgi/stat-pv") == 0) {
        req = RequestPtr(new PvRequest(r));
      } else if (strcmp(path, "/cgi/stat-rank") == 0) {
        req = RequestPtr(new RankRequest(r));
      }
    }
    return req;
  }
};

int main(){
  LOG_INIT("./article_stat.log", "DEBUG");
  string listen("127.0.0.1:19000");
  FcgiServer * fcgi_server = new FcgiServer(listen, 16);
  printf("start listen on %s\n", listen.c_str());
  // FcgiServer * fcgi_server = new FcgiServer("10.22.206.157:19000", 16);
  fcgi_server->RegisterRequestFactory(RequestFactoryPtr(new DemoRequestFactory()));
  fcgi_server->Start(true); // 本线程也处理 fcgi 请求，不退出
  return 0;
}

